module sample.knight.KnightApp;

import tango.io.Stdout;
import tango.stdc.stringz;
import tango.math.Math;

import sample.HordeApp;

void main()
{
	auto knight = new KnightApp();

	// Open window
	knight.openWindow();

	// Initialize application
	if (!knight.init())
	{
		Stderr("Failed to initialize Horde3D.").newline;
		return;
	}

	// Run the application
	knight.run();

	// Release the engine
	knight.release();
}

public class KnightApp : HordeApp
{
	// Engine objects
	private ResHandle pipeRes, logoMatRes, hdrPipeRes, forwardPipeRes;
	private NodeHandle knight, particleSys;

	private bool freeze;
	private float animTime;
	private float weight;

	private float x, y, z, rx, ry; // Viewer position and orientation
	private float velocity; // Velocity for movement

	public this()
	{
		x = 5;
		y = 3;
		z = 19;
		rx = 7;
		ry = 15;

		animTime = 0.0f;
		weight = 1.0f;
		velocity = 10.0f;
	}

	public override bool init()
	{
		if (!super.init())
		{
			return false;
		}

		// Add resources
		// Pipelines
		hdrPipeRes = Horde3D.addResource(ResourceTypes.Pipeline, "hdr.pipeline.xml", 0);
		forwardPipeRes = Horde3D.addResource(ResourceTypes.Pipeline, "forward.pipeline.xml", 0);
		// Logo
		logoMatRes = Horde3D.addResource(ResourceTypes.Material, "logo.material.xml", 0);
		// Environment
		ResHandle envRes = Horde3D.addResource(ResourceTypes.SceneGraph, "sphere.scene.xml", 0);
		// Knight
		ResHandle knightRes = Horde3D.addResource(ResourceTypes.SceneGraph, "knight.scene.xml", 0);
		ResHandle knightAnim1Res = Horde3D.addResource(ResourceTypes.Animation, "knight_order.anim", 0);
		ResHandle knightAnim2Res = Horde3D.addResource(ResourceTypes.Animation, "knight_attack.anim", 0);
		// Particle system
		ResHandle particleSysRes = Horde3D.addResource(ResourceTypes.SceneGraph, "particleSys1.scene.xml", 0);

		// Load resources
		Horde3DUtils.loadResourcesFromDisk("Content");

		// Add scene nodes
		// Add camera
		cam = Horde3D.addCameraNode(RootNode, "Camera", hdrPipeRes);
		Horde3D.setNodeParami(cam, CameraNodeParams.OcclusionCulling, 1);
		// Add environment
		NodeHandle env = Horde3D.addNodes(RootNode, envRes);
		Horde3D.setNodeTransform(env, 0, -20, 0, 0, 0, 0, 20, 20, 20);
		// Add knight
		knight = Horde3D.addNodes(RootNode, knightRes);
		Horde3D.setNodeTransform(knight, 0, 0, 0, 0, 180, 0, 0.1f, 0.1f, 0.1f);
		Horde3D.setupModelAnimStage(knight, 0, knightAnim1Res, "", false);
		Horde3D.setupModelAnimStage(knight, 1, knightAnim2Res, "", false);
		// Attach particle system to hand joint
		Horde3D.findNodes(knight, "Bip01_R_Hand", SceneNodeTypes.Joint);
		NodeHandle hand = Horde3D.getNodeFindResult(0);
		particleSys = Horde3D.addNodes(hand, particleSysRes);
		Horde3D.setNodeTransform(particleSys, 0, 40, 0, 90, 0, 0, 1, 1, 1);

		// Add light source
		NodeHandle light = Horde3D.addLightNode(RootNode, "Light1", 0, "LIGHTING", "SHADOWMAP");
		Horde3D.setNodeTransform(light, 0, 15, 10, -60, 0, 0, 1, 1, 1);
		Horde3D.setNodeParamf(light, LightNodeParams.Radius, 30);
		Horde3D.setNodeParamf(light, LightNodeParams.FOV, 90);
		Horde3D.setNodeParami(light, LightNodeParams.ShadowMapCount, 1);
		Horde3D.setNodeParamf(light, LightNodeParams.ShadowMapBias, 0.01f);
		Horde3D.setNodeParamf(light, LightNodeParams.Col_R, 1.0f);
		Horde3D.setNodeParamf(light, LightNodeParams.Col_G, 0.8f);
		Horde3D.setNodeParamf(light, LightNodeParams.Col_B, 0.7f);

		// Customize post processing effects
		ResHandle matRes = Horde3D.findResource(ResourceTypes.Material, "postHDR.material.xml");
		// hdrParams: exposure, brightpass threshold, brightpass offset (see shader for description)
		Horde3D.setMaterialUniform(matRes, "hdrParams", 2.5f, 0.5f, 0.08f, 0);

		return true;
	}

	protected override void render()
	{
		if (isShowingFPS())
		{
			char[] text = "Weigth: " ~ Float.format(new char[32], weight);
			Horde3DUtils.showText(toStringz(text), 0, 0.91f, 0.03f, 0, fontMatRes);
		}

		// Set camera parameters
		Horde3D.setNodeTransform(cam, x, y, z, rx, ry, 0, 1, 1, 1);
		// Show logo
		Horde3D.showOverlay(0.75f, 0, 0, 0, 1, 0, 1, 0, 1, 0.2f, 1, 1, 0.75f, 0.2f, 0, 1, 7, logoMatRes);
	}

	protected override void update()
	{
		super.update();
		
		float curVel = velocity / getFPS();

		if (wasKeyPressed(SDLK_ESCAPE))
		{
			setRunning(false);
		}

		if (wasKeyPressed(SDLK_SPACE))
		{
			toggleFreeze();
		}

		if (wasKeyPressed(SDLK_F3))
		{
			togglePipeline();
		}

		if (wasKeyPressed(SDLK_F7))
		{
			toggleDebugViewMode();
		}

		if (wasKeyPressed(SDLK_F8))
		{
			toggleWireframeMode();
		}

		if (wasKeyPressed(SDLK_F9))
		{
			toggleShowFPS();
		}

		if (isKeyDown(SDLK_LSHIFT))
		{
			curVel *= 5;
		}

		if (isKeyDown(SDLK_w))
		{
			// Move forward
			x -= sin(degToRad(ry)) * cos(-degToRad(rx)) * curVel;
			y -= sin(-degToRad(rx)) * curVel;
			z -= cos(degToRad(ry)) * cos(-degToRad(rx)) * curVel;
		}

		if (isKeyDown(SDLK_s))
		{
			// Move backward
			x += sin(degToRad(ry)) * cos(-degToRad(rx)) * curVel;
			y += sin(-degToRad(rx)) * curVel;
			z += cos(degToRad(ry)) * cos(-degToRad(rx)) * curVel;
		}

		if (isKeyDown(SDLK_a))
		{
			// Strafe left
			x += sin(degToRad(ry - 90)) * curVel;
			z += cos(degToRad(ry - 90)) * curVel;
		}
		if (isKeyDown(SDLK_d))
		{
			// Strafe right
			x += sin(degToRad(ry + 90)) * curVel;
			z += cos(degToRad(ry + 90)) * curVel;
		}
		if (isKeyDown(SDLK_1))
		{
			// Change blend weight
			weight += 2 / getFPS();
			if (weight > 1)
			{
				weight = 1;
			}
		}
		if (isKeyDown(SDLK_2))
		{
			// Change blend weight
			weight -= 2 / getFPS();
			if (weight < 0)
			{
				weight = 0;
			}
		}
		
		if (!freeze)
		{
			animTime += 1.0f / getFPS();

			// Do animation blending
			Horde3D.setModelAnimParams(knight, 0, animTime * 24.0f, weight);
			Horde3D.setModelAnimParams(knight, 1, animTime * 24.0f, 1.0f - weight);

			// Animate particle systems (several emitters in a group node)
			uint cnt = Horde3D.findNodes(particleSys, "", SceneNodeTypes.Emitter);
			for (uint i = 0; i < cnt; ++i)
			{
				Horde3D.advanceEmitterTime(Horde3D.getNodeFindResult(i), 1.0f / getFPS());
			}
		}
	}

	protected override void mouseMoved(int xrel, int yrel)
	{
		// Look left/right
		ry -= cast (float) xrel * 0.01 * 30;

		// Loop up/down but only in a limited range
		final float newX = cast (float) yrel * 0.01 * 30;
		rx += isInvertMouse() ? -newX : newX;
		
		clamp(rx, -90, 90);
	}
	
	private void toggleFreeze()
	{
		freeze = !freeze;
	}
	
	private void togglePipeline()
	{
		if (Horde3D.getNodeParami(cam, CameraNodeParams.PipelineRes) == hdrPipeRes)
		{
			Horde3D.setNodeParami(cam, CameraNodeParams.PipelineRes, forwardPipeRes);
		}
		else
		{
			Horde3D.setNodeParami(cam, CameraNodeParams.PipelineRes, hdrPipeRes);
		}
	}
	
	private void clamp(inout float value, float min, float max)
	in
	{
		assert (max > min);
	}
	body
	{
		if(value > max)
		{
			value = max;
		}
		else if(value < min)
		{
			value = min;
		}
	}
	
	private float degToRad(float f)
	{
		return f * (PI / 180.0f);
	}
}
